﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ThumbnailGenerator.cs" company="Microsoft Corporation">
//   Microsoft Corporation
// </copyright>
// <summary>
//   The thumbnail generator.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace ThumbGen
{
    using System;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.IO;

    using Microsoft.Expression.Encoder;

    /// <summary>
    /// The thumbnail generator.
    /// </summary>
    public class ThumbnailGenerator
    {
        #region Constants and Fields

        /// <summary>
        ///   The audio/video file.
        /// </summary>
        private AudioVideoFile m_audioVideoFile;

        /// <summary>
        /// The output directory to save the thumbnails.
        /// </summary>
        private string m_outputDirectory;

        /// <summary>
        ///   The time interval between the thumbnails. Defaults to 5 seconds.
        /// </summary>
        private TimeSpan m_timeInterval = new TimeSpan(0, 0, 0, 5);

        /// <summary>
        ///   The width to use for each thumbnail.
        /// </summary>
        private int m_width = 320;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        ///   Initializes a new instance of the ThumbnailGenerator class with a filename.
        /// </summary>
        public ThumbnailGenerator()
        {
        }

        /// <summary>
        /// Initializes a new instance of the ThumbnailGenerator class with a filename.
        /// </summary>
        /// <param name="fileName">
        /// The filename of the media file to look at.
        /// </param>
        public ThumbnailGenerator(string fileName)
        {
            this.FileName = fileName;
        }

        #endregion

        #region Public Events

        /// <summary>
        ///   The progress.
        /// </summary>
        public event EventHandler<ProgressEventArgs> Progress;

        #endregion

        #region Public Properties

        /// <summary>
        ///   Gets or sets FileName.
        /// </summary>
        public string FileName
        {
            get
            {
                return (this.m_audioVideoFile != null) ? this.m_audioVideoFile.FileName : null;
            }

            set
            {
                try
                {
                    this.m_audioVideoFile = new AudioVideoFile(value);
                    if (this.m_audioVideoFile.VideoStreams.Count == 0)
                    {
                        throw new ArgumentException("Couldn't find any video streams in the input file.");
                    }
                }
                catch (NotSupportedException exp)
                {
                    throw new ArgumentException(exp.Message, exp);
                }
                catch (InvalidMediaFileException exp)
                {
                    throw new ArgumentException(exp.Message, exp);
                }
            }
        }

        /// <summary>
        ///   Gets or sets the format to use to save the thumbnail.
        /// </summary>
        public Format Format { get; set; }

        /// <summary>
        ///   Gets or sets the name to be used as the prefix for the thumbnail filename.
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        ///   Gets or sets the directory that the thumbnails are placed into.
        /// </summary>
        public string OutputDirectory
        {
            get
            {
                return this.m_outputDirectory;
            }

            set
            {
                try
                {
                    EnsureDirectoryExists(value);
                }
                catch (NotSupportedException exp)
                {
                    throw new ArgumentException("Invalid output folder", exp);
                }
                catch (IOException exp)
                {
                    throw new ArgumentException("Invalid output folder", exp);
                }

                this.m_outputDirectory = value;
            }
        }

        /// <summary>
        ///   Gets or sets the time interval that used to generate the thumbnails.
        /// </summary>
        public TimeSpan TimeInterval
        {
            get
            {
                return this.m_timeInterval;
            }

            set
            {
                this.m_timeInterval = value;
            }
        }

        /// <summary>
        ///   Gets or sets the width to be used for each thumbnail. 320 is the default.
        /// </summary>
        public int Width
        {
            get
            {
                return this.m_width;
            }

            set
            {
                this.m_width = value;
            }
        }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets the actual output directory. If OutputDirectory is null then it will return
        ///   the current directory.
        /// </summary>
        private string ActualOutputDirectory
        {
            get
            {
                return this.OutputDirectory ?? Directory.GetCurrentDirectory();
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Go through and generate all thumbnails for the intput file into the output directory
        ///   at the specified time interval.
        /// </summary>
        public void GenerateThumbnails()
        {
            EnsureDirectoryExists(this.OutputDirectory);

            TimeSpan currentTime = TimeSpan.Zero;
            TimeSpan fileDuration = this.GetAudioVideoFile().Duration;
            double durationInSeconds = fileDuration.TotalSeconds;

            int index = 1;
            while (currentTime < fileDuration)
            {
                string fileName = this.GetThumbnailFileName(index);
                Bitmap bitmap = this.GetThumbnail(currentTime);

                // Get an ImageCodecInfo object that represents the JPEG codec.
                // ImageCodecInfo myImageCodecInfo = GetEncoder(ImageFormat.Png);
                if (this.Format == Format.Png)
                {
                    bitmap.Save(fileName, ImageFormat.Png);
                }
                else
                {
                    bitmap.Save(fileName, ImageFormat.Jpeg);
                }

                // Fire current progress
                this.OnProgress(currentTime.TotalSeconds / durationInSeconds);

                index++;
                currentTime += this.TimeInterval;
            }

            // Fire 100%
            this.OnProgress(1);
        }

        /// <summary>
        /// The get thumbnail.
        /// </summary>
        /// <param name="time">
        /// The time.
        /// </param>
        /// <returns>
        /// The thumbnail.
        /// </returns>
        public Bitmap GetThumbnail(TimeSpan time)
        {
            double aspectRatio = this.GetAudioVideoFile().VideoStreams[0].AspectRatio.Width
                                 / this.GetAudioVideoFile().VideoStreams[0].AspectRatio.Height;
            int height = (int)Math.Round(this.Width / aspectRatio);
            return this.GetAudioVideoFile().GetThumbnail(time, new Size(this.Width, height));
        }

        #endregion

        #region Methods

        /// <summary>
        /// Ensure the output directory exists.
        /// </summary>
        /// <param name="outputDirectory">
        /// The output Directory.
        /// </param>
        private static void EnsureDirectoryExists(string outputDirectory)
        {
            if (outputDirectory != null)
            {
                Directory.CreateDirectory(outputDirectory);
            }
        }

        /// <summary>
        /// Gets the AudioVideoFile instance, or throws an exception if one hasn't been created.
        /// </summary>
        /// <returns>
        /// The AudioVideoFile class.
        /// </returns>
        /// <exception cref="InvalidOperationException">
        /// Thrown if a valid file hasn't been set.
        /// </exception>
        private AudioVideoFile GetAudioVideoFile()
        {
            if (this.m_audioVideoFile == null)
            {
                throw new InvalidOperationException("No valid filename set.");
            }

            return this.m_audioVideoFile;
        }

        /// <summary>
        /// Get the filename to use for the thumbnail at the passed index.
        /// </summary>
        /// <param name="index">
        /// The index to use.
        /// </param>
        /// <returns>
        /// The filename to use.
        /// </returns>
        private string GetThumbnailFileName(int index)
        {
            string name;

            if (string.IsNullOrEmpty(this.Name))
            {
                name = Path.GetFileNameWithoutExtension(this.m_audioVideoFile.FileName);
            }
            else
            {
                name = this.Name;
            }

            name = string.Format("{0}_{1}.{2}", name, index, this.Format == Format.Png ? "png" : "jpg");

            return Path.Combine(this.ActualOutputDirectory, name);
        }

        /// <summary>
        /// Called to fire the on progress event.
        /// </summary>
        /// <param name="progress">
        /// The progress from 0 to 1.
        /// </param>
        private void OnProgress(double progress)
        {
            if (this.Progress != null)
            {
                this.Progress(this, new ProgressEventArgs(progress));
            }
        }

        #endregion
    }
}